查看原文
其他

Windows驱动编程之WFP/TDI

一半人生 看雪学苑 2022-07-01
本文为看雪论坛精华文章
看雪论坛作者ID:一半人生


本篇代码实战是WFP,TDI是铺垫,Windows网络杂谈开始。
 
XP老版本上,有几个驱动afd.sys,NetBt.sys,tdi.sys,tlnp.sys,tcpip.sys,ndis.sys等,Windows系统实现了OSI模型的4层,分别物理层,数据层,网际层和传输层。
 
Ndis好像分为三小层,没有写过代码这里不扩展,Win7后为了兼容老的TDI.sys,添加了新驱动TDX.sys,也保证了附加网络设备栈方式一致兼容,如/device/upd,/device/tcp等。Win7之后WFP是微软出来的新一代微型框架,为了更好的系统兼容性(稳定)和考虑上层过滤驱动开发更方便快捷。当然也怕hack不停Hook导致系统不稳定性和破坏,Minifilter诞生估计就有这小部分这个因素。
 
一个抽象的图:


1


TDI概念

TDI,Transport Driver Interface,传输驱动接口,连接着socket和协议驱动,是协议驱动实现,有些资料介绍是低层次内核态网络栈接口,用于访问传输层功能,比NDIS更接近于应用层。古老系统win2000、xp、03上的东西,虽然Vista之后不推荐使用该接口,但是现在来看都兼容,都会有TDI接口,所以是全平台不过以后可能会被微软剔除。
 
获取系统ip、端口数据,winapi就可以去做,如GetExtendedTcpTable 可以获取比较完善的数据信息。但并不能去截获包数据,写任意进程抓包器或端口抓包,三环HOOK必不可少,HOOK socket等系列函数,捕获发送的包过滤敏感信息。而在内核驱动层,可以创建过滤设备,但是要修改系统原生态的处理函数也可能用到HOOK操作,创建过滤设备能满足我们大多需求。
 
参考:http://www.codemachine.com/article_tdi.html 经典好文,当然是纯理论课本式,谷歌翻译就能学习,英语好底子好的越过本文的基础铺垫与TDI协议。


2


TDI基础铺垫 
下文tdi默认是xp环境,TCPIP.sys对上接口暴露的就是TDI,内核访问TCPIP栈会用到TDI函数编程,TDI成了内核唯一的socket接口。

TDI三类:


如上图所示,可以看到TDI Client,TDI Filter,TDI Transport,那么TDI Filter是可选的,其实这与以前键盘串口过滤本质是相同的,但是TDI有客户、过滤、传输三种。

1、Afd.sys,NetBt.sys,Http.sys都属于TDI客户端驱动。

2、过滤则是我们自己创建的过滤设备,防火墙就是一个最典型的过滤产品。

3、传输驱动通常实现传输层和网络层功能,处理TDI客户端IRP等,是通过NDIS网络适配访问,如TCPIP.sys,NWLINK.sys,TCPIP6.sys都是TDI驱动。


TDI设备对象:


说到windows,那就不能不提对象,进程也好、文件也罢,TDI也一样,对象如下:



3


TDI协议

很重要的一部分,看上述的图,Client到Transport,Filter可有可无,那么既然说了是网络,定然有交互协议约定。TDI传输依赖IRP与Callback,TDI Client使用IRP,而TDI Transport用EventCallback。

TDI事件通知:

1、TCP Client发送IRP到TCP Transport,这个地方TDI Transport返回STATUS_PENDING状态,之后在某个特定事件发生完成IRP,然后回调函数调用处理,这种是客户请求。主动注册需要发送TDI_RECEIVE_IRP胡策一个TDI_EVENT_RECEIVE回调。

2、另一种需要TDI提前注册好回调函数等待被触发,当特定事件发生时调用处理,这种是事件。


TDI IRP:

划重点,上图可以知道TDI使用IRP来TDIclient --> TDITransport请求,TDIClient驱动使用内部IO控制IRP,功能码IRP_MJ_INTERNAL_DEVICE_CONTROL发送TDI_xxx请求。IO stack Locations格式化了结构请求特定参数TDI_REQUEST_KERNEL_XXX。下述功能我个人化了一下,请大家看原文:


TDI Callback:

回调事件是为了优化内存使用,传输时候用底层网络驱动直接传输数据缓冲,整理如下:


拦截callback与irp不一样,irp拦截获取的是结构数据信息,irp包含所需的内容,而过滤事件先要拦截TDI_SET_EVENT_HANDLER请求,里面保存了TDIClient驱动注册的回调函数,替换实现拦截效果。

TDI Client side TDI Transport:


既然是连接两个端点,一个本地,一个远程。socket需要一一对应,当客户端请求,ip:port一致的话就会被TDI传输驱动在对象连接池中分配一个连接对象,有点NetworkPool的感觉多线程。
 
客户端交互TDI:
1、ZwCreateFile创建传输对象(本地)与连接对象
2、TDI_ASSOCIATE_ADDRESS关联,这样类似于映射机制绑定在一起
3、发送连接请求TDI_CONNECT
4、发送断开请求TDI_DISCONNECT
5、TDI_DISASSOCIATE_ADDRESS解绑,销毁两者之间绑定关系
6、ZwClose关闭传输对象与连接对象
服务端交互TDI:

1、ZwCreateFile创建传输地址对象

2、注册事件

3、创建一系列连接端点对象ZwCreateFile

4、关键起来TDI_ASSOCIATE_ADDRESS

5、当需要处理函数,被调用时,检查远端信息,选择可用的远程连接端去发起响应请求(TDI_ACCEPT)


4


WFP概念


概念性请参考msdn:
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/network/roadmap-for-developing-wfp-callout-drivers


5


WFP实战

wfp-tcp抓包:


本篇是以前学习编写的demo示例,Wfp再应用层和内核层都提供了API,本套代码逻辑User负责添加Callout,Sublayer及filter,内核层负责注册Callout。

r3:
1、 wfp引擎初始化如下,HlprFwpmEngineOpen负责打开引擎:
// OpenEngineresult = HlprFwpmEngineOpen(&engineHandle, &session);if (NO_ERROR != result){ printf("Error: HlprFwpmEngineOpen = %d\r\n", result); return -1;}
// TransactionBeginresult = HlprFwpmTransactionBegin(&engineHandle);if (NO_ERROR != result){ printf("Error: HlprFwpmTransactionBegin = %d\r\n", result); return -1;}
RtlZeroMemory(&callout, sizeof(FWPM_CALLOUT));RtlZeroMemory(&displayData, sizeof(FWPM_DISPLAY_DATA));displayData.description = MONITOR_FLOW_ESTABLISHED_CALLOUT_DESCRIPTION;displayData.name = MONITOR_FLOW_ESTABLISHED_CALLOUT_NAME;

FwpmCalloutAdd0添加CallOut,函数原型如下:
_IRQL_requires_max_(PASSIVE_LEVEL)NTSTATUSNTAPIFwpmCalloutAdd0( _In_ HANDLE engineHandle, _In_ const FWPM_CALLOUT0* callout, _In_opt_ PSECURITY_DESCRIPTOR sd, _Out_opt_ UINT32* id );

2、 函数有一个重要参数就是callout,FWPM_CALLOUT0结构原型如下,calloutKey是128bit的GUID唯一标识,是标记添加的CallOut,displayData则是CallOut的名字和描述,applicableLayer是Callout添加到哪一个层:
typedef struct FWPM_CALLOUT0_ { GUID calloutKey; FWPM_DISPLAY_DATA0 displayData; UINT32 flags; /* [unique] */ GUID *providerKey; FWP_BYTE_BLOB providerData; GUID applicableLayer; UINT32 calloutId; } FWPM_CALLOUT0;

3、 监控Tcp流量,第一步使用CallOutAdd添加两个Callout,GUID分别如下:
FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4/V6 关联TCP上下文数据包FWPM_LAYER_STREAM_V4/V6 监控发送流量,如send/sendto

4、添加完成Callout之后,使用FwpmSubLayerAdd添加子层和过滤条件,这里添加一个MONITOR_SAMPLE_SUBLAYER的子层:

5、FWPM_SUBLAYER用于添加一个子层,FWPM_FILTER添加一个过滤器,而FWPM_FILTER_CONDITION用于添加过滤条件,它可以指定多个过滤条件,定义成为数组:
FWPM_SUBLAYER TcpSubLayer, UdpSubLayer;// FWPM_SUBLAYER monitorUdpICMPSubLayer;FWPM_FILTER Tcpfilter, Udpfilter;FWPM_FILTER_CONDITION TcpSublayerfilterConditions[3] = { 0, }, UdpSublayerfilterConditions[3] = { 0, }; // We only need two for this call.

6、过滤条件:TcpSublayerfilterConditions有几个重要参数,conditionValue.uint8表示IPPROTO_TCP符合TCP才会触发。
过滤条件:RtlZeroMemory(TcpSublayerfilterConditions, sizeof(TcpSublayerfilterConditions)); TcpSublayerfilterConditions[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL; TcpSublayerfilterConditions[0].matchType = FWP_MATCH_EQUAL; // 是否等于条件值 TcpSublayerfilterConditions[0].conditionValue.type = FWP_UINT8; TcpSublayerfilterConditions[0].conditionValue.uint8 = IPPROTO_TCP; // TCP

7、过滤器:Tcpfilter.SubLayerKey参数关联第三步添加的子层,Tcpfilter.filterCondition关联第四步添加的过滤器,这个概念就像子层里面有多个过滤器,过滤器有多个过滤条件:
RtlZeroMemory(&Tcpfilter, sizeof(FWPM_FILTER)); Tcpfilter.layerKey = FWPM_LAYER_ALE_FLOW_ESTABLISHED_V4; Tcpfilter.displayData.name = L"Flow established filter."; Tcpfilter.displayData.description = L"Sets up flow for traffic that we are interested in."; Tcpfilter.action.type = FWP_ACTION_CALLOUT_TERMINATING; // FWP_ACTION_CALLOUT_INSPECTION Tcpfilter.action.calloutKey = TCP_FLOW_ESTABLISHED_CALLOUT_V4; Tcpfilter.filterCondition = TcpSublayerfilterConditions; Tcpfilter.subLayerKey = MONITOR_SAMPLE_SUBLAYER; Tcpfilter.weight.type = FWP_EMPTY; // auto-weight. Tcpfilter.numFilterConditions = 1;
printf("HlprFwpmFilterAdd engineHandle = 0x%p\n", *engineHandle); result = HlprFwpmFilterAdd(engineHandle, &Tcpfilter); if (NO_ERROR != result) break;
用户态负责添加Callout,添加子层和绑定过滤器及添加过滤条件,这套代码同样可以再内核层实现,这里是写成了应用层。
 
协议栈流量经过我们注册的子层,通过匹配过滤器及条件筛选,符合条件将流量交给添加的CallOut处理,应用层只添加了Callout,还少至关重要的一步,流量符合条件流入Callout之后如何处理?放行-拦截-转发代理?接下来内核态注册Callout。

r0:
1、FwpsCalloutRegister函数负责注册Callout生效,函数原型如下,参数1对象,参数2:FWPS_CALLOUT结构,参数3 ID标识:
#if (NTDDI_VERSION >= NTDDI_WIN7)// Register the function pointers for a version-1 callout. The callout driver// must call FwpsCalloutUnregisterById before unloading._IRQL_requires_max_(PASSIVE_LEVEL)NTSTATUSNTAPIFwpsCalloutRegister1( _Inout_ void* deviceObject, _In_ const FWPS_CALLOUT1* callout, _Out_opt_ UINT32* calloutId );#endif // (NTDDI_VERSION >= NTDDI_WIN7)

2、FWPS_CALLOUT包含了处理回调,子层命中数据流进行处理,calloutKey指定那一层的数据,比如TCP_FLOW_ESTABLISHED_CALLOUT_V4(应用层的第三步),ClassifyFnction回调函数负责处理流量数据:
FWPS_CALLOUT sCallout;NTSTATUS status = STATUS_SUCCESS;memset(&sCallout, 0, sizeof(FWPS_CALLOUT));sCallout.calloutKey = *calloutKey;sCallout.flags = flags;sCallout.classifyFn = ClassifyFunction;sCallout.notifyFn = NotifyFunction;sCallout.flowDeleteFn = FlowDeleteFunction;

3、分别来看FWPM_LAYER_STREAM_CALLOUT | FWPM_LAYER_ALE_FLOW_ENTABLISHED的ClassifyFunction
 
3.1 FWPM_LAYER_ALE_FLOW_ENTABLISHED:
 
该层中两个重要参数可以获取"五要素"数据,进行上线文关联。
_In_ const FWPS_INCOMING_VALUES* inFixedValues,_In_ const FWPS_INCOMING_METADATA_VALUES* inMetaValues,

这里介绍参数中那些可能关心的数据:
进程路径inMetaValues->processPath五要素inFixedValues->incomingValue[index].value.uint32;incomingValue是一个数组,index获取不同的元素:typedef enum FWPS_FIELDS_ALE_FLOW_ESTABLISHED_V4_{ FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_APP_ID, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_USER_ID, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_ADDRESS_TYPE, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_PORT, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_PROTOCOL, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_ADDRESS, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_REMOTE_PORT, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_REMOTE_USER_ID, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_REMOTE_MACHINE_ID, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_DESTINATION_ADDRESS_TYPE, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_IP_LOCAL_INTERFACE, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_DIRECTION, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_INTERFACE_TYPE, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_TUNNEL_TYPE,#if (NTDDI_VERSION >= NTDDI_WIN6SP1) FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_FLAGS,#if (NTDDI_VERSION >= NTDDI_WIN8) FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_ORIGINAL_APP_ID, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_PACKAGE_ID,#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD) FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_ALE_SECURITY_ATTRIBUTE_FQBN_VALUE,#if (NTDDI_VERSION >= NTDDI_WIN10_RS2) FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_COMPARTMENT_ID,#endif // (NTDDI_VERSION >= NTDDI_WIN10_RS2)#endif // (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)#endif // (NTDDI_VERSION >= NTDDI_WIN8)#endif // (NTDDI_VERSION >= NTDDI_WIN6SP1) FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_LOCAL_ADDRESS, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_LOCAL_PORT, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_REMOTE_ADDRESS, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_BITMAP_IP_REMOTE_PORT, FWPS_FIELD_ALE_FLOW_ESTABLISHED_V4_MAX} FWPS_FIELDS_ALE_FLOW_ESTABLISHED_V4;

申请结构体,通过回调参数获取元素保存再结构体中,并且通过FwpsFlowAssociateContext关联至其它层。
status = FwpsFlowAssociateContext(flowHandle, FWPS_LAYER_STREAM_V4, // 关联到stream_v4层 streamId, // 层id flowContextLocal); // 保存的元素

3.2 FWPM_LAYER_STREAM_CALLOUT :
 
Established层中已经将ip-port等数据关联至该层,所以从第三个参数可以获取到关联的数据。
_Inout_opt_ void* packet

将packet转换成FWPS_STREAM_CALLOUT_IO_PACKET结构体,然后获取数据长度和数据包,进行数据解析和打印,这里可以做过滤拦截,比如根据IP-PORT可以选择动作,放行或拦截。
streamPacket = (FWPS_STREAM_CALLOUT_IO_PACKET*)packet; if (streamPacket->streamData != NULL && streamPacket->streamData->dataLength != 0){ flowData = *(FLOW_DATA**) (UINT64*)&flowContext; inbound = (BOOLEAN)((streamPacket->streamData->flags &FWPS_STREAM_FLAG_RECEIVE) == FWPS_STREAM_FLAG_RECEIVE); DbgPrint("Tcp --> ProcessId : %d\t Protor: %d\t localAddressV4: 0x%x:%d\t remoteAddressV4: 0x%x:%d\r\n", flowData->processID, flowData->ipProto, flowData->localAddressV4, flowData->localPort, flowData->remoteAddressV4, flowData->remotePort);

放行设置FWP_ACTION_CONTINUE:
classifyOut->actionType = FWP_ACTION_CONTINUE;

拦截设置FWP_ACTION_BLOCK:
需要转才可以 ULONG blockipaddrArry[10] = { 0xb465310b,0, }; if (flowData->remoteAddressV4 == blockipaddrArry[0]) { classifyOut->actionType = FWP_ACTION_BLOCK; return status; }

wfp业务场景通常和代理(重定向)有关,一种是发起连接时候直接重定向,注册connect redirect层。
FPWM_LAYER_ALE_CONNECT_REDIRECT

另一种需要改包,将原数据包复制且block,然后处理复制的包(修改),重新注入层发送,微软也提供了一个udp代理,有兴趣的可以学习,后面有时间分享一些商用驱动框架,如NetFilter SDK2.0。
 
以前的旧笔记,和其它两篇驱动是姐妹篇:

1、Windows驱动编程之串口过滤杂谈: https://xz.aliyun.com/t/6487

2、Windows驱动编程之键盘过滤杂谈: https://xz.aliyun.com/t/6581



 


看雪ID:一半人生

https://bbs.pediy.com/user-home-819685.htm

*本文由看雪论坛 一半人生 原创,转载请注明来自看雪社区





# 往期推荐

1. 记一次MEMZ样本分析

2. Dex起步探索

3. cocos2d逆向入门和某捕鱼游戏分析

4. Windows驱动编程之NetFilter SDK

5. GlobeImposter家族的病毒样本分析

6. CVE-2010-2553 堆溢出漏洞分析



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存